package org.apache.maven.lifecycle.internal;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.lifecycle.DefaultLifecycles;
import org.apache.maven.lifecycle.LifeCyclePluginAnalyzer;
import org.apache.maven.lifecycle.Lifecycle;
import org.apache.maven.lifecycle.mapping.LifecycleMapping;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @since 3.0
 * @author Benjamin Bentmann
 * @author Jason van Zyl
 * @author jdcasey
 * @author Kristian Rosenvold (extracted class only)
 *         <p/>
 *         NOTE: This class is not part of any public api and can be changed or deleted without
 *         prior notice.
 */
@Component(role = LifeCyclePluginAnalyzer.class)
public class DefaultLifecyclePluginAnalyzer implements LifeCyclePluginAnalyzer {

  @Requirement(role = LifecycleMapping.class)
  private Map<String, LifecycleMapping> lifecycleMappings;

  @Requirement
  private DefaultLifecycles defaultLifeCycles;

  @Requirement
  private Logger logger;

  public DefaultLifecyclePluginAnalyzer() {
  }

  // These methods deal with construction intact Plugin object that look like they come from a
  // standard
  // <plugin/> block in a Maven POM. We have to do some wiggling to pull the sources of information
  // together and this really shows the problem of constructing a sensible default configuration but
  // it's all encapsulated here so it appears normalized to the POM builder.

  // We are going to take the project packaging and find all plugin in the default lifecycle and
  // create
  // fully populated Plugin objects, including executions with goals and default configuration taken
  // from the plugin.xml inside a plugin.
  //

  public Set<Plugin> getPluginsBoundByDefaultToAllLifecycles(String packaging) {
    if (logger.isDebugEnabled()) {
      logger.debug("Looking up lifecyle mappings for packaging " + packaging + " from "
          + Thread.currentThread().getContextClassLoader());
    }

    LifecycleMapping lifecycleMappingForPackaging = lifecycleMappings.get(packaging);

    if (lifecycleMappingForPackaging == null) {
      return null;
    }

    Map<Plugin, Plugin> plugins = new LinkedHashMap<Plugin, Plugin>();

    for (Lifecycle lifecycle : getOrderedLifecycles()) {
      org.apache.maven.lifecycle.mapping.Lifecycle lifecycleConfiguration = lifecycleMappingForPackaging.getLifecycles()
          .get(lifecycle.getId());

      Map<String, String> phaseToGoalMapping = null;

      if (lifecycleConfiguration != null) {
        phaseToGoalMapping = lifecycleConfiguration.getPhases();
      } else if (lifecycle.getDefaultPhases() != null) {
        phaseToGoalMapping = lifecycle.getDefaultPhases();
      }

      if (phaseToGoalMapping != null) {
        // These are of the form:
        //
        // compile -> org.apache.maven.plugins:maven-compiler-plugin:compile[,gid:aid:goal,...]
        //
        for (Map.Entry<String, String> goalsForLifecyclePhase : phaseToGoalMapping.entrySet()) {
          String phase = goalsForLifecyclePhase.getKey();
          String goals = goalsForLifecyclePhase.getValue();
          if (goals != null) {
            parseLifecyclePhaseDefinitions(plugins, phase, goals);
          }
        }
      }
    }

    return plugins.keySet();
  }

  private List<Lifecycle> getOrderedLifecycles() {
    // NOTE: The lifecycle order can affect implied execution ids so we better be deterministic.

    List<Lifecycle> lifecycles = new ArrayList<Lifecycle>(defaultLifeCycles.getLifeCycles());

    Collections.sort(lifecycles, new Comparator<Lifecycle>() {

      public int compare(Lifecycle l1, Lifecycle l2) {
        return l1.getId().compareTo(l2.getId());
      }

    });

    return lifecycles;
  }

  private void parseLifecyclePhaseDefinitions(Map<Plugin, Plugin> plugins, String phase, String goals) {
    String[] mojos = StringUtils.split(goals, ",");

    for (int i = 0; i < mojos.length; i++) {
      GoalSpec gs = parseGoalSpec(mojos[i].trim());

      if (gs == null) {
        logger.warn("Ignored invalid goal specification '" + mojos[i] + "' from lifecycle mapping for phase " + phase);
        continue;
      }

      Plugin plugin = new Plugin();
      plugin.setGroupId(gs.groupId);
      plugin.setArtifactId(gs.artifactId);
      plugin.setVersion(gs.version);

      Plugin existing = plugins.get(plugin);
      if (existing != null) {
        if (existing.getVersion() == null) {
          existing.setVersion(plugin.getVersion());
        }
        plugin = existing;
      } else {
        plugins.put(plugin, plugin);
      }

      PluginExecution execution = new PluginExecution();
      execution.setId(getExecutionId(plugin, gs.goal));
      execution.setPhase(phase);
      execution.setPriority(i - mojos.length);
      execution.getGoals().add(gs.goal);

      plugin.getExecutions().add(execution);
    }
  }

  private GoalSpec parseGoalSpec(String goalSpec) {
    GoalSpec gs = new GoalSpec();

    String[] p = StringUtils.split(goalSpec.trim(), ":");

    if (p.length == 3) {
      // <groupId>:<artifactId>:<goal>
      gs.groupId = p[0];
      gs.artifactId = p[1];
      gs.goal = p[2];
    } else if (p.length == 4) {
      // <groupId>:<artifactId>:<version>:<goal>
      gs.groupId = p[0];
      gs.artifactId = p[1];
      gs.version = p[2];
      gs.goal = p[3];
    } else {
      // invalid
      gs = null;
    }

    return gs;
  }

  private String getExecutionId(Plugin plugin, String goal) {
    Set<String> existingIds = new HashSet<String>();
    for (PluginExecution execution : plugin.getExecutions()) {
      existingIds.add(execution.getId());
    }

    String base = "default-" + goal;
    String id = base;

    for (int index = 1; existingIds.contains(id); index++) {
      id = base + '-' + index;
    }

    return id;
  }

  static class GoalSpec {

    String groupId;

    String artifactId;

    String version;

    String goal;

  }

}
